package com.efrei.uchat.server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.util.Observable;

import org.apache.log4j.Logger;

import com.efrei.uchat.exception.UChatException;
import com.efrei.uchat.io.Session;
import com.efrei.uchat.io.Sessions;
import com.efrei.uchat.message.CroomMessage;
import com.efrei.uchat.message.Headers;
import com.efrei.uchat.message.LoginMessage;
import com.efrei.uchat.message.Message;
import com.efrei.uchat.model.croom.CroomMessageStatus;
import com.efrei.uchat.model.user.Chatter;
import com.efrei.uchat.utils.Const;

public class ClientListener extends Observable implements Runnable {
	
	private static final Logger logger = Logger.getLogger(ClientListener.class);
	
	private Session session;
	private Socket client;
	private BufferedReader input;
	
	private enum ClientListenerStatus {
		/**
		 * connection init with login
		 */
		INIT,
		/**
		 * waiting client's messages
		 */
		WAITING_MESSAGE
	};
	private ClientListenerStatus status = ClientListenerStatus.INIT;
	
	public ClientListener(Session session) throws IOException {
		this.session = session;
		this.client = session.getClient();
		this.input = new BufferedReader(
				new InputStreamReader(client.getInputStream())
				);
	}

	@Override
	public void run() {
		logger.info("ClientListener open");
		
		// read messages
		try {
			readMessages();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (UChatException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		// socket closed, disconnect
		try {
			session.disconnect();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		logger.info("end of ClientListener");
	}

	/**
	 * received socket's message and dispatch to manageMessage
	 * @throws IOException
	 * @throws UChatException
	 * @throws InterruptedException
	 */
	private void readMessages() throws IOException, UChatException, InterruptedException {
		Message message;
		String data;
		
		while((data = input.readLine()) != null) {
			message = Message.fromString(data);
			manageMessage(message);
		}
	}
	
	/**
	 * Message dispatcher
	 * @param message
	 * @throws UChatException
	 * @throws IOException
	 */
	protected void manageMessage(Message message) throws UChatException, IOException {
		int id = message.getSessionId();
		
		logger.debug("dispatch message "+message.toString());
		
		if (status == ClientListenerStatus.INIT) {
			// waiting first login message
			if(id != Const.UNKNOWN_SESSION_ID
					|| message.getType() != Headers.LOGIN) {
				throw new UChatException("Invalid unknown session id");
			}
			// new login
			if(initConnection((LoginMessage) message)) {
				setChanged();
				notifyObservers(
						new CroomMessage(
								CroomMessageStatus.JOIN,
								Const.PUBLIC_CROOM,
								null, session.getId()));
			}
		} else {
			// filter with message type
			setChanged();
			notifyObservers(message);
		}
	}
	
	/**
	 * try to register new User
	 * @param loginMessage
	 * @throws IOException
	 */
	private boolean initConnection(LoginMessage loginMessage) throws IOException {
		Chatter chatter = new Chatter(loginMessage.getLogin());
		boolean success = false;
		
		logger.info("initConnection");
		// register this login
		try {
			session.getServer().getSessionsLock().lock();
			logger.debug("LOCK sessions");
			try {
				Sessions sessions = session.getServer().getSessions();
				if(!sessions.getChatters().isRegistered(chatter)) {
					// available login with session registration in server's sessions
					session.setChatter(chatter);
					sessions.sessionCompleted(session);
					success = true;
					status = ClientListenerStatus.WAITING_MESSAGE;
				}
			} finally {
				logger.debug("UNLOCK sessions");
				session.getServer().getSessionsLock().unlock();
			}
			
			logger.info("return "+success);
			// send answer and generated id
			session.send(
					new LoginMessage(chatter.getPseudo(), session.getId()).
					returnAnswer(success));
		} catch(InterruptedException e) {
			logger.error("Lock sessions failed");
		} catch (UChatException e) {
			logger.error("Register new user failed");
		}
		
		return success;
	}

	/**
	 * manage a loginMessage
	 * @param message
	 * @return 0 if connection failed, 1 if register is a success, 2 if rename was a failure
	 * @throws InterruptedException
	 * @throws UChatException
	 */
	protected int manageLoginMessage(LoginMessage message) throws InterruptedException, UChatException {
		Chatter chatter = new Chatter(message.getLogin());
		int id = message.getSessionId();
		
		// register this login
		session.getServer().getSessionsLock().lock();
		logger.debug("LOCK sessions");
		try {
			Sessions sessions = session.getServer().getSessions();
			
			if(id == Const.UNKNOWN_SESSION_ID) {
				throw new UChatException(
						String.format("Invalid sessionId %s received", id));
			} else {
				Session session = sessions.getSession(id);
				if(session == null) {
					throw new UChatException(
							String.format("Invalid sessionId %s received", id));
				}
				chatter = session.getChatter();
				String oldPseudo = chatter.getPseudo();

				// rename
				if(sessions.getChatters().rename(
						chatter,
						message.getLogin())) {
					// valid rename
					logger.info(
							String.format("Session %s: rename in %s",
									oldPseudo, chatter.getPseudo()));
					return 1;
				} else {
					// invalid rename
					return 2;
				}
			}
		} finally {
			logger.debug("UNLOCK sessions");
			session.getServer().getSessionsLock().unlock();
		}
	}
	
	@Override
	public String toString() {
		return String.format("ClientListener %s", client);
	}
}
